Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Databases tree view #245

Merged
merged 6 commits into from
Nov 19, 2024
Merged

Databases tree view #245

merged 6 commits into from
Nov 19, 2024

Conversation

danvergara
Copy link
Owner

@danvergara danvergara commented Nov 15, 2024

Add a view to see databases and their tables

Description

The vast majority of database client software, if not all, show a list of databases and their tables as a tree structure. This is convenient for users that want to see all the database the have access to.
When it comes to dblab, things used to be a bit different, because database parameter was always required, either as a cli flag value or as part of the DSN. This was this way for the longest time because one reason. First off, dblab was heavily inspired by pgweb. This was the client I used to use at work, but suddenly I got tasked to manage a couple of microservices tied to MySQL databases, and I needed a FOSS alternative for Linux that worked as well as pgweb for my use case. pgweb rquires a database to connect to and only display its tables as a list. Then I tried to mimic a pgweb like client in the terminal and that's how dblab started. But things have changed I my needs have evolved and sometimes I need to look at other databases as well.

This MR basically removes the mandatory status of the database parameter and makes it optional. If the database is not provided, then, a tree structure is displayed showig databases and their tables.

Another important change has been made and it was removing the pagination. I have rarely found myself using it, because it's faster to make query to where I need to be.

Screenshot from 2024-11-15 23-36-45

Screenshot from 2024-11-15 23-42-24

Fixes #209
Fixes #232

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

Tested it against pagila databse and the testing database of the project

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have checked my code and corrected any misspellings

@danvergara danvergara self-assigned this Nov 16, 2024
@danvergara danvergara marked this pull request as ready for review November 16, 2024 06:05
Comment on lines 149 to 161
var (
resultSet = [][]string{}
db *sqlx.DB
)

if c.activeDatabase != "" {
switch c.driver {
case drivers.Postgres, drivers.PostgreSQL, drivers.MySQL:
db = c.dbs[c.activeDatabase]
}
} else {
db = c.db
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about this?

Suggested change
var (
resultSet = [][]string{}
db *sqlx.DB
)
if c.activeDatabase != "" {
switch c.driver {
case drivers.Postgres, drivers.PostgreSQL, drivers.MySQL:
db = c.dbs[c.activeDatabase]
}
} else {
db = c.db
}
var (
resultSet = [][]string{}
)
db = c.db
if c.activeDatabase != "" {
switch c.driver {
case drivers.Postgres, drivers.PostgreSQL, drivers.MySQL:
db = c.dbs[c.activeDatabase]
}
}

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is so nice and elegant. Good catch!

if c.activeDatabase != "" {
switch c.driver {
case drivers.Postgres, drivers.PostgreSQL, drivers.MySQL:
db = c.dbs[c.activeDatabase]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about this?

Suggested change
db = c.dbs[c.activeDatabase]
db, ok = c.dbs[c.activeDatabase]
if !ok {
return nil, nil, return nil, fmt.Errorf("connection with %s database not found", c.activeDatabase)
}

I'm afraid about possible panic, when calling db.Query

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good catch there!

I'll add this.

Thanks!

func (c *Client) PreviousPage() (*Table, int, error) {
if err := c.paginationManager.PreviousPage(); err != nil {
return nil, 0, err
tables = append(tables, table)
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each row.Nect loop must be followed by checking rows.Err()

Suggested change
}
}
if err:= rows.Err(); err != nil {
return nil, err
}

return c.db
}
databases = append(databases, database)
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue about missing rows.Err check

Comment on lines 449 to 459
if driver == drivers.MySQL {
newConnString = strings.Replace(connString, "/", fmt.Sprintf("/%s", database), 1)
} else {
u, err := url.Parse(connString)
if err != nil {
return nil, err
}

u.Path = "/" + database
newConnString = u.String()
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use a switch and return an error when it's none of the supported database types

I'm a bit worried by the fact you will apply the postgres way to any future database type that might be added

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with using a switch here, but the driver validation is done somewhere. If the execution reaches this point, the driver is actually valid. Regarding applying the postgres way, yeah a switch statement might help supporting a new use case.

db, err := sqlx.Open(driver, newConnString)
if err != nil {

panic(err)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why a panic?

Suggested change
panic(err)
return nil, err

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left it there by accident. I'll remove it,
Thanks!

@@ -39,7 +39,15 @@ func TestMain(m *testing.M) {
func generateURL(driver string) string {
switch driver {
case drivers.Postgres:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why postgresql const is not present here? Isn't it tested?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the that constant is the one used at the time to test on CI. I can only use one at a time there.

Comment on lines 288 to 289
assert.Greater(t, len(r), 0)
assert.Greater(t, len(co), 0)
Copy link

@ccoVeille ccoVeille Nov 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert.Greater(t, len(r), 0)
assert.Greater(t, len(co), 0)
assert.NotEmpty(t, r)
assert.NotEmpty(t, co)

Comment on lines 314 to 315
assert.Greater(t, len(r), 0)
assert.Greater(t, len(co), 0)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same you could use NotEmpty

pkg/tui/tui.go Outdated
Comment on lines 422 to 426
// if i == 0 {
// t.aw.content.SetCell(i+1, j, &tview.TableCell{Text: sc, Color: tcell.ColorRed})
// } else {
// }
t.aw.content.SetCellSimple(i+1, j, sc)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why leaving commented out code?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

I was testing out new colors.

@danvergara
Copy link
Owner Author

Hey @ccoVeille!

What a good code review there!

I already addressed all your comments. Let me know if I missed something.

Thanks so much!

P.S. Can I include you as a reviewer from this point on?

@ccoVeille
Copy link

Hey @ccoVeille!

What a good code review there!

Thanks, you are welcome

I already addressed all your comments. Let me know if I missed something.

Thanks so much!

P.S. Can I include you as a reviewer from this point on?

Sure

@ccoVeille
Copy link

Maybe you could add a co-authored-by tag

You can find my email here

samber/lo@52a73c3

So, just add this to the commit message of the changes you made with my review

Co-authored-by: ccoVeille <[email protected]>

Comment on lines +471 to +478
default:
u, err := url.Parse(connString)
if err != nil {
return nil, err
}

u.Path = "/" + database
newConnString = u.String()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be safer (I might have forgotten some)

Suggested change
default:
u, err := url.Parse(connString)
if err != nil {
return nil, err
}
u.Path = "/" + database
newConnString = u.String()
case drivers.PostgreSQL, drivers.Postgres:
u, err := url.Parse(connString)
if err != nil {
return nil, err
}
u.Path = "/" + database
newConnString = u.String()
default:
return nil, fmt.Errorf("unsupported driver %s", driver)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree with this, since the driver validation is done somewhere else and the MySQL URL parsing is the corner case here The default behavior is parsing the DSN with url.Parse.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah OK then. I misunderstood

Comment on lines 343 to 344
assert.Greater(t, len(m.Indexes.Rows), 0)
assert.Greater(t, len(m.Indexes.Columns), 0)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use NotEmpty

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

testifylint linter could help you a lot with such "issue"

It would also detect when you should use require.NoError

https://github.com/Antonboom/testifylint

It can be added easily to golangci-lint, bit you don't have golangci-lint yet,
maybe

I can help you here if you want

Comment on lines +204 to 207
c, err := New(opts)
assert.NoError(t, err)

tables, err := c.ShowTables()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here you should (must) use

Suggested change
c, err := New(opts)
assert.NoError(t, err)
tables, err := c.ShowTables()
c, err := New(opts)
require.NoError(t, err)
tables, err := c.ShowTables()

require is another package available in testify

The reason is that if an error is returned by New, c will be nil, so c.ShowTables will panic

Test should never panic

Copy link
Owner Author

@danvergara danvergara Nov 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, let's focus on the scope of the PR. We can address the test suite in another PR and issue. This test suite is not new, it's been 3 years since I set it up. I'm willing to improve it but later.

@@ -212,23 +221,20 @@ func TestTableStructure(t *testing.T) {
Pass: password,
Host: host,
Port: port,
DBName: name,
Schema: schema,
SSL: "disable",
Limit: 100,
}

c, _ := New(opts)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't ignore the error here

@@ -247,17 +253,14 @@ func TestConstraints(t *testing.T) {

c, _ := New(opts)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

Comment on lines +284 to +285
t.Logf("constraints columns %v", co)
t.Logf("constraints content %v", r)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These logs are not needed. If the test fail, you will get the info. If it doesn't fail, I don't see a need to see these lines when running a go test

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's create an issue to improve the test suite.

@danvergara
Copy link
Owner Author

Maybe you could add a co-authored-by tag

You can find my email here

samber/lo@52a73c3

So, just add this to the commit message of the changes you made with my review

Co-authored-by: ccoVeille <[email protected]>

Alright @ccoVeille, I just added the co-authored tag. Honor when honor is due.

Can you go over the PR again, please? I 'd like to land this change tonight.

Copy link
Collaborator

@Arturomtz8 Arturomtz8 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice work man

@danvergara danvergara merged commit a0a6fdc into main Nov 19, 2024
8 checks passed
@danvergara danvergara deleted the databases-tree-view branch November 19, 2024 06:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[FEATURE] Creating a database [FEATURE] show database catalogue?
3 participants